package base.jdk8_demo.lambda.demo06;


import base.jdk8_demo.lambda.demo02.Emp;
import org.junit.Test;

import java.io.PrintStream;
import java.util.Arrays;
import java.util.Comparator;
import java.util.function.*;

/**
 *
 * 注意：函数式接口的实现可以由lambda表达式、方法引用、构造器引用等方式实现。
 *
 * 从中我们可以知道：
 * 1. 一个函数式接口有且只有一个抽象方法。
 * 2. default 修饰的方法不是抽象方法, 默认已经实现。
 * 3. 重写了超类Object类中任意一个public方法的方法并不算接口中的抽象方法。
 * 4. static方法不属于抽象方法。
 *
 * 下面开始学习 方法引用 和 构造器引用 以及 数组引用
 * 方法引用：若 lambda 体中的内容有方法已经实现，我们可以使用“方法引用”，换句话说就是用已有的方法来作为lambda实现且格式是新颖的。
 *
 * 方法引用主要有3种语法格式：方法引用就是对函数式接口的具体实现
 * 对象::实例方法名
 * 类::静态方法名
 * 类::实例方法名
 *
 * 方法引用注意事项：
 * 1、前提条件是：已实现的方法和函数式接口(包括jdk8提供的4大函数式接口以及我们自定义函数式接口)
 *    中方法入参及返回值保持一致
 *
 * 2、类::实例方法名，只适用于 lambda 参数列表第一个参数是实例方法的调用者，而第二个参数是实例方法的参数时，
 *    可以使用 ClassName::method
 *
 *
 * 构造器引用：用于创建对象
 * 格式：ClassName::new
 * 注意事项：需要调用的构造器的参数列表要与函数式接口中抽象方法的参数列表保持一致。
 *
 *
 * 数组引用：用于创建数组对象
 * 格式： Type[]::new
 *
 * notice
 * 总结:
 * 1.对于方法引用,如果一步不能做到如何写成方法引用的形式,可以先写出lambda表达式,对lambda表达式
 * 进行改造成对应的方法引用形式.
 * 2.方法引用很灵活,方法引用就是对函数接口的具体实现(匿名内部类).
 * 对象::实例方法名形式要满足条件:已实现的方法和函数式接口中方法入参及返回值保持一致
 * 类::静态方法名形式要满足条件:已实现的方法和函数式接口中方法入参及返回值保持一致
 * 类::实例方法名形式要满足条件:对于Predicate断言型，lambda 参数列表第一个参数是实例方法的调用者，
 * 而第二个参数是实例方法的参数
 *
 */
public class TestMethodRef {

    // 构造器引用：构造对象之用
    @Test
    public void testCase5(){
        Supplier<Emp> sup = ()-> new Emp();
        Emp emp = sup.get();
        System.out.println(emp);

        Supplier<Emp> sup2 = Emp::new;
        Emp emp1 = sup2.get();
        System.out.println(emp1);
    }
    @Test
    public void testCase6(){
        Function<String, Emp> fun = (x) -> new Emp(x);

        // 调用一个参数的构造器，且参数是String类型，返回Emp类型，与 Function函数式接口方法保持一致。
        Function<String, Emp> fun2 = Emp::new;
        Emp zs = fun2.apply("zs");
        System.out.println(zs);


        BiFunction<String, Integer, Emp> bf = (x,y) -> new Emp(x, y);

        BiFunction<String, Integer, Emp> bf2 = Emp::new;
        Emp ls = bf2.apply("ls", 18);
        System.out.println(ls);
    }

    // 数组引用
    @Test
    public void testCase7(){
        Function<Integer, String[]> fun = (x) -> new String[x];

        Function<Integer, String[]> fun2 = String[]::new;
        String[] arr = fun2.apply(10);
        System.out.println(arr.length);
        System.out.println(Arrays.toString(arr));
    }

    // 对象::实例方法名
    /**
     * lambda体中方法已由PrintStream实现，因此可以改造。
     *
     * 前提条件是：已实现的方法和函数式接口中方法入参及返回值保持一致，在这里演示的是
     * void ps.println(String) 和函数式接口方法void Consumer.accept(T t) 保持了一致。
     */
    @Test
    public void testCase1(){
        PrintStream ps = System.out;
        Consumer<String> consumer = (str) -> ps.println(str); //函数式接口由lambda表达式实现

        consumer = ps::println;//函数式接口由方法引用实现.
        consumer.accept("abcdef");
    }

    @Test
    public void testCase2(){
        Emp emp = new Emp("张三");
        Supplier<String> supplier = emp::getName; //函数式接口由方法引用实现
        System.out.println(supplier.get());
    }

    // 类：：静态方法名
    @Test
    public void testCase3(){
        Comparator<Integer> com = (x,y) -> Integer.compare(x , y);//函数式接口由lambda表达式实现

        // 方法引用方式
        Comparator<Integer> com2 = Integer::compare; //函数式接口由方法引用实现
//         notice TransformCollection.testList2Map() => Function::identity
        int compare = com2.compare(1, 2);
        System.out.println(compare);
    }

    // 类：：实例方法名
    /**
     * notice
     * x.equals(y) 与 BiPredicate 中的函数式接口 boolean test(T t, U u)方法并没有结构上的相似性,
     * 但前者可以看做是后者的一种实现, 相当于test(T , U)内部是通过 T.equals(U)实现的
     */
    @Test
    public void testCase4(){
        BiPredicate<String, String> bp = (x,y)-> x.equals(y);//函数式接口由lambda表达式实现
//        BiPredicate<String, String> bp = (x,y)-> x.equals(y)==true;
        /**
         * 类::实例方法名 这种方法引用的前提条件是：
         * 对于Predicate断言型，lambda 参数列表第一个参数是实例方法的调用者，而第二个参数是实例方法的参数时
         * 可以使用 ClassName::method
         *
         */
        BiPredicate<String, String> bp2 = String::equals;//函数式接口由方法引用实现
        boolean boo = bp2.test("zjw", "zjw");
        System.out.println(boo);
    }

}
